import * as dfd from 'danfojs-node';
import path from 'path';
import fs from 'fs';
import { IpcMainInvokeEvent } from 'electron';
import { FileType } from '../const';
import Log from './Log';
import CommonUtil from './common-util';
import ProgressUtil from './progress-util';
import { Task } from '../biz/ipc/feature.const';
import XLSX from 'xlsx';

const TAG = 'DataFrameUtil';

class DataFrameUtil {
  private static instance: DataFrameUtil;

  public static getInstance() {
    if (!this.instance) {
      this.instance = new DataFrameUtil();
    }
    return this.instance;
  }

  public readExcel = (filePath: string): Promise<dfd.DataFrame> => {
    return dfd.readExcel(filePath) as Promise<dfd.DataFrame>;
  };

  public query(df: dfd.DataFrame, colName: string, value: string) {
    if (colName === null || value === null) {
      return null;
    }
    const qeury = df.query(df[colName].eq(value));
    // 查找到
    if (qeury.shape[0] > 0) {
      return qeury.iloc({ rows: [0] });
    }
    return null;
  }

  public concatVertical(dfList: dfd.DataFrame[]) {
    return dfd.concat({ dfList, axis: 0 });
  }

  public getColumnDeduplicate = (
    column1: string[],
    column2: string[],
    prefix: string
  ): string[] => {
    const res: string[] = [];
    Object.values(column1).forEach((c1) => {
      res.push(c1);
    });
    Object.values(column2).forEach((c2) => {
      let find = false;
      Object.values(res).forEach((r) => {
        if (c2 === r) {
          find = true;
        }
      });
      if (find) {
        res.push(`${prefix}_${c2}`);
      } else {
        res.push(c2);
      }
    });
    return res;
  };

  public mergeDataFrame(
    df1: dfd.DataFrame,
    df2: dfd.DataFrame,
    twoCols: string[]
  ) {
    // 返回值
    let res: dfd.DataFrame = new dfd.DataFrame([], {
      columns: this.getColumnDeduplicate(df1.columns, df2.columns, 'df2'),
    });
    // 列名
    const colName1 = twoCols[0];
    const colName2 = twoCols[1];
    Object.values(df1.index).forEach((rowIndex) => {
      const val1 = df1.at(rowIndex, colName1) as string;
      const val2 = df1.at(rowIndex, colName2) as string;
      // 查询
      const query1 = this.query(df2, colName1, val1);
      const query2 = this.query(df2, colName1, val2);
      const query3 = this.query(df2, colName2, val1);
      const query4 = this.query(df2, colName2, val2);
      const querys = [query1, query2, query3, query4];
      // 检查查询结果
      Object.values(querys).forEach((q) => {
        if (q != null) {
          const concatRes = dfd.concat({
            dfList: [df1.loc({ rows: [rowIndex] }), q],
            axis: 1,
          }) as dfd.DataFrame;
          if (res.shape[0] === 0) {
            res = concatRes;
          } else {
            res = res.append(concatRes, res.shape[0] + 1) as dfd.DataFrame;
          }
        }
      });
    });
    return res;
  }

  public doTwoColsMerge = (
    filePath1: string,
    filePath2: string,
    twoCols: string[],
    exportCols: string[]
  ): Promise<dfd.DataFrame> => {
    const p1 = dfd.readExcel(filePath1) as Promise<dfd.DataFrame>;
    const p2 = dfd.readExcel(filePath2) as Promise<dfd.DataFrame>;
    return Promise.all([p1, p2])
      .then(([df1, df2]) => {
        return this.mergeDataFrame(df1, df2, twoCols).loc({
          columns: exportCols,
        }) as dfd.DataFrame;
      })
      .catch((err) => {
        console.log(err);
      }) as Promise<dfd.DataFrame>;
  };

  public verticalMerge = (
    event: IpcMainInvokeEvent,
    task: Task,
    scanDir: string,
    exportDir: string
  ) => {
    let xlsxList = CommonUtil.getInstance().getAllFiles(scanDir, FileType.XLSX);
    const invaildList = CommonUtil.getInstance().getAllFilesExclude(
      scanDir,
      FileType.XLSX
    );
    Log.i(TAG, 'check password protected');
    const passwordProtectedList: string[] = [];
    for (let i = 0; i < xlsxList.length; i++) {
      const filePath = xlsxList[i];
      try {
        XLSX.readFile(filePath);
      } catch (err) {
        Log.e(TAG, err);
        passwordProtectedList.push(filePath);
        invaildList.push(filePath);
      }
    }
    xlsxList = xlsxList.filter((item) => {
      return passwordProtectedList.indexOf(item) == -1;
    });
    Log.i(TAG, 'xlsxList', xlsxList);
    Log.i(TAG, 'invaildList', invaildList);
    Log.i(TAG, 'start copy invaildList');
    const invaildDest = path.join(exportDir, 'InvaildFiles');
    if (!fs.existsSync(invaildDest)) {
      fs.mkdirSync(invaildDest);
    }
    invaildList.forEach((invaildFile) => {
      const fileName = path.basename(invaildFile);
      const destPath = path.join(invaildDest, fileName);
      fs.copyFile(invaildFile, destPath, () => {});
    });
    Log.i(TAG, 'start doVerticalMerge');
    const readExcelPomiseList: Promise<dfd.DataFrame>[] = [];
    const exportMap: Map<string, { df: dfd.DataFrame; filePath: string }[]> =
      new Map();
    xlsxList.forEach((filePath) => {
      try {
        const readExcelPomise = DataFrameUtil.getInstance()
          .readExcel(filePath)
          .then((df) => {
            const md5 = CommonUtil.getInstance().getArrMd5(df.columns);
            const md5FileName = `${md5}.${FileType.XLSX}`;
            if (exportMap.has(md5FileName)) {
              exportMap.get(md5FileName)?.push({ df, filePath });
            } else {
              exportMap.set(md5FileName, [{ df, filePath }]);
            }
            return df;
          })
          .catch((err) => {
            Log.e(TAG, err);
          }) as Promise<dfd.DataFrame>;
        readExcelPomiseList.push(readExcelPomise);
      } catch (err) {
        Log.e(TAG, err);
      }
    });
    Promise.all(readExcelPomiseList)
      .then((res) => {
        let i = 0;
        const resultLog: Array<{
          outputFileName: string;
          originFiles: string[];
        }> = [];
        for (const key of exportMap.keys()) {
          const dfInfoList = exportMap.get(key);
          const dfList = dfInfoList?.map((item) => {
            return item.df;
          });
          const filePathList = dfInfoList?.map((item) => {
            return item.filePath;
          });
          if (filePathList) {
            resultLog.push({ outputFileName: key, originFiles: filePathList });
          }
          if (dfList) {
            const concatRes = this.concatVertical(dfList);
            const exportPath = path.join(exportDir, key);
            dfd.toExcel(concatRes, {
              filePath: exportPath,
            });
          }
          Log.i(TAG, `NO.${i} task, total task size: ${exportMap.size}`);
          ProgressUtil.getInstance().sendProgress({
            task,
            progress: (i + 1) / exportMap.size,
          });
          i += 1;
        }
        fs.writeFile(
          path.join(exportDir, 'ResultLog.txt'),
          JSON.stringify(resultLog, null, 2),
          () => {}
        );
      })
      .catch((err) => {
        Log.e(TAG, err);
      });
  };
}
export default DataFrameUtil;
